<?php
/**
 * Manuscript Comparator
 * Copyright (C) 2009  OpenScriptures.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 
 * @todo Add persistant position of unchecked MSS via cookie
 * @todo Add links to Wikipedia;
 * @todo Tell Terveen and others that whole NT is now in the system
 * @todo Query log: get by popularity
 * @todo User authentication: use OpenID... and MySQL users? What is the best practice here?
 * 
 * Applications:
 *  # Comparing two manuscripts side-by-side, with slinks between equivalents and highlighted diffs
 *  # Viewing unified diff of two manuscripts (containing tokens only from the two manuscripts, not the entire unified manuscript), where primary manuscript's unique material is INS and secondary is DEL (primary's tokens are displayed among equivalents)
 *  # Viewing the entire unified-manuscript with all variants highlighted, with color-coding (perhaps yellow background, but a colored underline or outline for each manuscript that attests to it). Words that are common among all manuscripts appear normally.
 *  # Blindly populate token_parsing with morph information (raw information including Strong's, lemma, parsing), to be replaced with James and Ulrik's latice so that we can mouse over a word and see the parsing
 *  # Include a unified manuscript viewer, which highlights all words that do not occur in every manuscript
 *  - Resurrect slinking tool, to get help from Nate and others... dump to static text file which can be imported? The manuscript view should be of the UMS (link to UMS)
 *  - Include sample translation linking (ESV and RVA and NIV and KJV)... have a text file that includes all of the necessary words for building DS (so we can rebuild)
 *
 */

$rev = intval(substr('$Rev: 4$', 5));

ini_set('memory_limit', '100M'); //should be no greater than 30M actually used
$startTime = microtime(true);
define('TOKENS_PER_PAGE', 2500);
define('DEFAULT_PASSAGE', '1 John 5');
define('DEFAULT_INS_QUANTITY', 2); //4

require_once('../reference-parser.lib.php');
require_once('../svn.lib.php');

/**
 * Load JSON encoding in case it is not natively available
 */
if(!function_exists('json_encode')){
	require_once '../JSON.php';
    function json_encode($data) {
		static $json;
		if(!isset($json))
			$json = new Services_JSON();
        return( $json->encode($data) );
    }
}


require('../../db_config.php');
mysql_connect(DB_HOST, DB_USER, DB_PASSWORD); //defined in db_config.php
mysql_select_db(DB_NAME);

//Get all of the manuscripts in the system
$manuscriptQuery = mysql_query("SELECT id,
									   url,
									   (SELECT data FROM text WHERE work.name = text.id AND language = 'en') AS name,
									   (SELECT data FROM text WHERE work.copyright = text.id AND language = 'en') AS copyright
									FROM work
									WHERE type IN('manuscript','manuscript-edition')");

//Get all of the manuscripts
$manuscripts = array();
$manuscriptsByID = array();
while($manuscript = mysql_fetch_object($manuscriptQuery)){
	if($manuscript->id == 1 || $manuscript->id == 3) //German Bible Society wants NA27/UBS4 removed
		continue;
	$manuscriptsByID[$manuscript->id] = $manuscript;
	$manuscripts[] = &$manuscriptsByID[$manuscript->id];
}

//Create default $insManuscripts and $delManuscripts
$insManuscripts = array();
$delManuscripts = array();
$remainingManuscripts = array(); //TODO: perhaps there should also be a UL#unusedMss
if(empty($_REQUEST['ins']) || !is_array($_REQUEST['ins']) || empty($_REQUEST['del']) || !is_array($_REQUEST['del'])){
	$isDefaultMSS = true;
	for($i = 0; $i < count($manuscripts); $i++){
		$manuscripts[$i]->checked = true;
		if($i < DEFAULT_INS_QUANTITY)
			$insManuscripts[] = $manuscripts[$i];
		else
			$delManuscripts[] = $manuscripts[$i];
	}
}
//Reconstitute $insManuscripts and $delManuscripts
else {
	$isDefaultMSS = false;
	$_REQUEST['ins'] = array_unique($_REQUEST['ins'] );
	foreach($_REQUEST['ins'] as $msID){
		if(!empty($manuscriptsByID[$msID])){
			$manuscriptsByID[$msID]->checked = true;
			$insManuscripts[] = $manuscriptsByID[$msID];
		}
	}
	
	$_REQUEST['del'] = array_unique($_REQUEST['del'] );
	foreach($_REQUEST['del'] as $msID){
		if(!empty($manuscriptsByID[$msID])){
			$manuscriptsByID[$msID]->checked = true;
			$delManuscripts[] = $manuscriptsByID[$msID];
		}
	}
	
	foreach($manuscripts as &$manuscript){
		if(empty($manuscript->checked)){
			$remainingManuscripts[] = $manuscript;
		}
	}
}


//Gather the IDs for the two sets of manuscripts
$insIDs = array();
foreach($insManuscripts as $manuscript){
	$insIDs[] = (int)$manuscript->id;
}
$insIDs_str = join(', ', $insIDs);
$delIDs = array();
foreach($delManuscripts as $manuscript){
	$delIDs[] = (int)$manuscript->id;
}
$delIDs_str = join(', ', $delIDs);
$allIDs = array_merge($insIDs, $delIDs); //the original order for all MSS as indicated in the query form
$allIDs_str = join(', ', $allIDs);

$unifiedManuscript = mysql_fetch_object(mysql_query("SELECT workUMS.* FROM work workUMS INNER JOIN work work1 ON work1.unified_work = workUMS.id WHERE work1.id = " . $insManuscripts[0]->id));

$view = (@$_REQUEST['view'] == 'unified' ? 'unified' : 'parallel');
$hideDivisions = isset($_REQUEST['hide-divisions']);
$includeStrongs = isset($_REQUEST['strongs']) || empty($_GET);

/**
 * Calculate the location of the requested passage; and set up the request.
 **/
$canonicalSelf = '';
$isRequest = false;
$isMultipage = false;
$requestCacheKey = '';
if(isset($_REQUEST['passage'])){
	$isRequest = true;
	
	//Parse the requested passage and normalize it for display
	$osisref = parse_reference($_REQUEST['passage']);
	if(!$osisref){
		header("content-type: text/plain", true, 400);
		print "Unable to parse: " . $_REQUEST['passage'];
		exit;
	}
	$displayPassage = format_osisref($osisref);
	
	//Get osisRef components
	@list($osisRefStart, $osisRefEnd) = split('-', $osisref, 2);
	$osisEndRef = $osisRefEnd ? $osisRefEnd : $osisRefStart;
	preg_match('/^(\w+)(?:\.(\d+)(?:\.(\d+))?)?/', $osisRefStart, $matches);
	$startBook = $matches[1];
	$startChapter = isset($matches[2]) ? $matches[2] : 1;
	$startVerse = isset($matches[3]) ? $matches[3] : 1;
	preg_match('/^(\w+)(?:\.(\d+)(?:\.(\d+))?)?/', $osisEndRef, $matches);
	$endBook = $matches[1];
	$endChapter = isset($matches[2]) ? $matches[2] : 1;
	$endVerse = isset($matches[3]) ? $matches[3] : 1;
	
	//Get the start and end token positions according to the osisref provided
	$startPosition = mysql_fetch_row(mysql_query(
				"SELECT MIN(tokenUMS.position)
					FROM token tokenMS
					INNER JOIN token tokenUMS ON tokenUMS.id = tokenMS.ums_token
					INNER JOIN ref ON tokenMS.id = ref.start_token
					WHERE tokenMS.work IN ($allIDs_str) AND osis_id = \"" . mysql_real_escape_string($osisRefStart) . "\""));
	if($startPosition)
		$startPosition = $startPosition[0];
	$endPosition = mysql_fetch_row(mysql_query(
				"SELECT MAX(tokenUMS.position)
					FROM token tokenMS
					INNER JOIN token tokenUMS ON tokenUMS.id = tokenMS.ums_token
					INNER JOIN ref ON tokenMS.id = ref.end_token
					WHERE tokenMS.work IN ($allIDs_str) AND osis_id = \"" . mysql_real_escape_string($osisEndRef) . "\""));
	if($endPosition)
		$endPosition = $endPosition[0];
	
	//Detect any errors
	if(!$endPosition || !$startPosition){
		header("content-type: text/plain", true, 400);
		print "Did not understand the osisRef: $osisref";
		exit;
	}
	
	//Add support for paging results that contain too much information
	$page = (int)@$_REQUEST['page'];
	if(!$page)
		$page = 1;
	$originalStartPosition = $startPosition;
	$originalEndPosition = $endPosition;
	if($endPosition - $startPosition > TOKENS_PER_PAGE){
		$startPosition += ($page-1)*TOKENS_PER_PAGE;
		$endPosition = min($startPosition + TOKENS_PER_PAGE, $originalEndPosition);
		$isMultipage = true;
	}
	
	//See if the passage exists
	if(!mysql_num_rows(mysql_query("SELECT id FROM token WHERE work = $unifiedManuscript->id AND position = $startPosition LIMIT 1"))){
		header("content-type: text/plain", true, 400);
		print "There is no page $page for the reference: $osisref";
		exit;
	}
	if($originalEndPosition < $startPosition){
		header("content-type: text/plain", true, 400);
		print "Your ending reference is greater than your starting reference: $osisref";
		exit;
	}
	
	//Set the request object which is serialized into a JSON string and then Base64'd... this is used as the cache filename 
	$requestCacheKey = new stdClass();
	$requestCacheKey->view = $view;
	$requestCacheKey->ref = $osisref;
	$requestCacheKey->ins = $insIDs;
	$requestCacheKey->del = $delIDs;
	$requestCacheKey->strongs = $includeStrongs;
	$requestCacheKey->div = $hideDivisions;
	$requestCacheKey->page = $page;
	$requestCacheKey = rtrim(base64_encode(json_encode($requestCacheKey)), '=');
	$requestCacheKey .= '.' . filemtime(__FILE__); //".$rev.html";
	
	//Construct canonical URI
	$canonicalSelf = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . "?passage=$osisref&view=$view";
	if($includeStrongs)
		$canonicalSelf .= "&strongs=1";
	if($hideDivisions)
		$canonicalSelf .= "&hide-divisions=1";
	if($page > 1)
		$canonicalSelf .= "&page=$page";
	if(!$isDefaultMSS){
		$canonicalSelf .= "&" . urlencode('ins[]') . "=" . join('&' . urlencode('ins[]') . '=', $insIDs);
		$canonicalSelf .= "&" . urlencode('del[]') . "=" . join('&' . urlencode('del[]') . '=', $delIDs);
	}
}

/**
 * Print the next (1) and previous (-1) link if $isMultipage and if the current page allows it
 */
function print_adjacent_link($pos = 1){
	global $page, $isMultipage, $originalEndPosition, $endPosition;
	if(!$isMultipage)
		return;
	$adjacentGET = $_GET;
	if(!($pos > 0 && $endPosition < $originalEndPosition) && !($pos < 0 && $page > 1))
		return;
	$adjacentGET['page'] = $page + $pos;

	//Consruct query string
	$qsParts = array();
	foreach($adjacentGET as $name => $value){
		if(is_array($value)){
			foreach($value as $v){
				$qsParts[] = urlencode($name) . "[]=" . urlencode($v);
			}
		}
		else {
			$qsParts[] = urlencode($name) . "=" . urlencode($value);
		}
	}
	
	//Print links
	if($pos > 0)
		print "<nav class='paged next'><a href=\"./?" . htmlspecialchars(join('&', $qsParts)) . "\" rel='next'>Next page &gt;</a></nav>";
	else
		print "<nav class='paged prev'><a href=\"./?" . htmlspecialchars(join('&', $qsParts)) . "\" rel='prev'>&lt; Previous page</a></nav>";
}

/* Serve output ******/
if(strpos(@$_SERVER['HTTP_ACCEPT'], 'xhtml') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false)
	header('content-type: application/xhtml+xml; charset=utf-8');
else
	header('content-type: text/html; charset=utf-8');

#$rev = intval(preg_replace('/^\D+/', '', '$Rev: 214 $'));
echo '<'.'?xml version="1.0" encoding="utf-8"?'.">\n";

?>
<!DOCTYPE html><!-- XHTML5 -->
<!--
Copyright 2009, OpenScriptures.org
GPL 3 license: http://www.gnu.org/licenses/gpl.html
-->
<html xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="http://www.bibletechnologies.net/2003/OSIS/namespace">
	<head>
		<!--[if IE]>
		<meta charset="utf-8" />
		<script>
		//HTML5 enabling script for IE. For discussion and comments, see: http://remysharp.com/2009/01/07/html5-enabling-script/
		(function(){if(!/*@cc_on!@*/0)return;var e = "abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,header,mark,menu,meter,nav,output,progress,section,time,video".split(','),i=e.length;while (i-=1){document.createElement(e[i])}})()
		</script>
		<![endif]-->
		<title><?php
		if($isRequest){
			echo htmlspecialchars($displayPassage);
			if($isMultipage)
				echo " (page $page) ";
			echo ' | ';
		}
		?>Manuscript Comparator by Open Scriptures</title>
		<meta name="description" content="Allows multiple (edited) manuscripts to be compared with each other in side-by-side or unified views." />
		<meta name="keywords" content="manuscript, manuscripts, mss, comparer, comparison, compare, comparative, diff, dissimilarity, similarity, likeness, resemblance, uniformity, uniformness, parallel, unified" />
		<link rel="stylesheet" type="text/css" href="all.css?<?php echo filemtime('all.css') ?>" />
		<link rel="icon" type="image/png" href="/favicon.png" />
		<?php if($canonicalSelf): ?>
		<link rel="canonical self" href="<?php echo htmlspecialchars($canonicalSelf) ?>" />
		<?php endif; ?>
	</head>
	<body>
		<!--[if IE]>
		<p style="background-color:#FFCCCC; padding:1em; border:dotted 1px red; margin-left:auto; margin-right:auto; ">
			<strong>Notice:</strong> This tool does not function optimally in Internet Explorer. If possible load it in
			<a href="http://www.mozilla.com/firefox/">Mozilla Firefox</a>,
			<a href="http://www.apple.com/safari/">Apple Safari</a>,
			<a href="http://www.opera.com/">Opera</a>,
			or
			<a href="http://www.google.com/chrome">Google Chrome</a> <small>(administrator rights not needed to install Chrome)</small>.
		</p>
		<![endif]-->
		
		<p style="padding:1em; border:dotted 2px red; background-color:#FFCCCC;" id="morphgnt-notice">The MorphGNT with UBS4 has been removed. <a title="Licensing and the German Bible Society @ Open Scriptures Google Group" href="http://groups.google.com/group/open-scriptures/browse_thread/thread/f57701ff851ae905">Learn why…</a></p>
		
		<h2><a rel="home" href="/">Open Scriptures</a></h2>
		<h1><?php if($isRequest): ?><a href='./' rel="contents"><?php endif; ?>Manuscript Comparator<?php if($isRequest): ?></a>: <?php
			echo htmlspecialchars($displayPassage);
			if($isMultipage)
				echo " (page $page) ";
		?><?php endif; ?></h1>
		<noscript>
			<p><em>You should have JavaScript enabled to use this application.</em></p>
		</noscript>
		<form id="configuration" <?php if($isRequest): ?>class='collapsed'<?php endif; ?> action="./" method="get">
			<fieldset><legend><?php if($isRequest): ?><a href='#configuration'><?php endif; ?>Configuration<?php if($isRequest): ?></a><?php endif; ?></legend>

			<?php include('introduction.php') ?>

			<p><label for="passage">Passage: </label><input type="text" name="passage" id="passage" value="<?php echo empty($_REQUEST['passage']) ? DEFAULT_PASSAGE : @htmlspecialchars($_REQUEST['passage']) ?>" /> <samp>e.g. John 3:16, Rom 5:5-8, Mt 1-2, or an osisRef</samp></p>

			<p class='view'><label for="view">View: </label><?php
			$views = array(
				'parallel' => "Parallel (side-by-side)",
				'unified'  => "Unified (inline)"
			);
			?><select id="view" name="view"><?php
			foreach($views as $value => $text){
				print "<option value='$value'";
				if($view == $value)
					print ' selected="selected"';
				print ">";
				print $text;
				print "</option>";
			}
			?></select></p>

			<?php if($isDefaultMSS): ?>
			<p><input type="checkbox" disabled="disabled" id="use-default-mss" <?php if($isDefaultMSS): ?> checked="checked" <?php endif; ?> /><label for="use-default-mss">Use default manuscript arrangement (as follows).</label></p>
			<?php endif; ?>
			<!--<p>Select the desired manuscripts and arrange them in <b class='help' title='Where manuscripts agree, the common word from a manuscript with a higher precedence will be displayed instead of those of lower preference. This matters because manuscripts have differences in casing, diacritics, and puctuation. Furthermore, the reference system of the first selected manuscript will be used in the unified view.'>order of preference</b>:</p>-->
			<?php
			//Display the insertion manuscripts and the deletion manuscripts
			print "<ol id='insList' class='msList'>"; # title='Manuscripts whose unique contents will appear as insertions.'
			foreach($insManuscripts as $manuscript){
				print "<li id='li$manuscript->id'>";
				print "<input id='ms$manuscript->id' name='ins[]' value='$manuscript->id' type='checkbox'";
				if(!empty($manuscript->checked))
					print " checked='checked'";
				if($isDefaultMSS)
					print " disabled='disabled'";
				print " />";
				print "<label for='ms$manuscript->id'>" . htmlspecialchars($manuscript->name) . "</label>";
				print "</li>";
			}
			print "</ol>";
			print "<ol id='delList' class='msList' start='" . (count($insManuscripts)+1) . "'>";  # title='Manuscripts whose unique contents will appear as deletions.'
			foreach(array_merge($delManuscripts, $remainingManuscripts) as $manuscript){
				print "<li id='li$manuscript->id'>";
				print "<input id='ms$manuscript->id' name='del[]' value='$manuscript->id' type='checkbox'";
				if(!empty($manuscript->checked))
					print " checked='checked'";
				if($isDefaultMSS)
					print " disabled='disabled'";
				print " />";
				print "<label for='ms$manuscript->id'>" . htmlspecialchars($manuscript->name) . "</label>";
				print "</li>";
			}
			print "</ol>";
			?>

			<p><input id="include-strongs" name="strongs" value="1" type="checkbox" <?php if($includeStrongs): ?> checked="checked" <?php endif; ?> /><label for="include-strongs">Load Strong's definitions and derivations <small>(increases load time)</small>.</label></p>
			<p><input id="hide-divisions" name="hide-divisions" value="1" type="checkbox" <?php if($hideDivisions): ?> checked="checked" <?php endif; ?> /><label for="hide-divisions">Hide chapter and verse divisions<!-- <small>(taken from first <abbr title="manuscript">MS</abbr> above when in unified view)</small>-->.</label></p>
			<!--
			<p><input disabled="disabled" id="include-punctuation" name="punctuation" value="1" type="checkbox" <?php if(!empty($_REQUEST['punctuation']) || empty($_GET)): ?> checked="checked" <?php endif; ?> /><label for="include-punctuation">Show paragraphs, commas, periods, and other punctuation.</label></p>
			Exclude variants (those that appear in brackets)?
			-->
			<p class='buttons'>
				<button type="submit">Display</button>
				<!--<button type="reset">Reset</button>-->
			</p>
			</fieldset>
		</form>

<?php
if(!empty($isRequest)){
	$tokenQueryDuration = 0;
	
	//Load the results if they are cached
	if($requestCacheKey && ($cache = @file_get_contents("./cache/$requestCacheKey"))){
		print $cache;
	}
	//Otherwise serve the results:
	else {
		ob_start();
		
		$tokenSQL = '';
		$tokenQuery = null;
		
		$queriedManuscripts = array();
		
		$manuscriptGroup = new stdClass();
		$manuscriptGroup->side = 'ins';
		$manuscriptGroup->these = &$insManuscripts;
		$manuscriptGroup->those = &$delManuscripts;
		$queriedManuscripts[] = $manuscriptGroup;
		
		$manuscriptGroup = new stdClass();
		$manuscriptGroup->side = 'del';
		$manuscriptGroup->those = &$insManuscripts;
		$manuscriptGroup->these = &$delManuscripts;
		$queriedManuscripts[] = $manuscriptGroup;
			
		?>
		<hr />
		<p id="loadingNotice"><em>Loading...</em></p>

		<?php
		$tokenInfo = array();
		
		//Add SQL for obtaining the references
		$selectRef = ", ins_ref.type AS ins_ref_type, ins_ref.osis_id AS ins_ref_osis_id";  #ins_ref.work AS ins_ref_work, 
		$leftJoinRef = "LEFT JOIN ref ins_ref ON ins_ref.type = 'verse' AND ins_ref.start_token = token_ins.id AND ins_ref.work = " . $insManuscripts[0]->id;
		if($view == 'parallel'){ //In the parallel view, we need to select refs from both the top INS and DEL, but if we're unified, then just select INS and use it only
			$selectRef .= ", del_ref.type AS del_ref_type, del_ref.osis_id AS del_ref_osis_id"; #del_ref.work AS del_ref_work, 
			$leftJoinRef .= " LEFT JOIN ref del_ref ON del_ref.type = 'verse' AND del_ref.start_token = token_del.id AND del_ref.work = " . $delManuscripts[0]->id;
		}
		
		/*
		OLD MSS LINKING: Tokens from manuscript 1 are connected to tokens in manuscript 2 via the tokens in the unified manuscript (UMS)
		
		token1
			clusterToken1
				cluster1
					group1
				cluster2
			clusterToken2
		tokenUMS
			clusterToken3
				cluster3
					group2
				cluster4
			clusterToken4
		token2
		*/

#This single SQL query is used for both the unified and parallel views
$tokenSQL = <<<SQL
SELECT DISTINCT
       tokenUMS.id AS tokenUMS_id, tokenUMS.data AS tokenUMS_data, tokenUMS.position AS tokenUMS_position,
       token_ins.id AS token_ins_id,
	   token_ins.work AS token_ins_work,
       token_ins.data AS token_ins_data,
	   token_ins_parsing.id      AS token_ins_parsing_id,
	   token_ins_parsing.strongs AS token_ins_parsing_strongs,
	   token_ins_parsing.lemma   AS token_ins_parsing_lemma,
	   token_ins_parsing.parse   AS token_ins_parsing_parse,
	   token_ins_parsing.work    AS token_ins_parsing_work,
	   token_del.id AS token_del_id,
	   token_del.work AS token_del_work,
       token_del.data AS token_del_data,
	   token_del_parsing.id      AS token_del_parsing_id,
	   token_del_parsing.strongs AS token_del_parsing_strongs,
	   token_del_parsing.lemma   AS token_del_parsing_lemma,
	   token_del_parsing.parse   AS token_del_parsing_parse,
	   token_del_parsing.work    AS token_del_parsing_work
	   $selectRef
    FROM        token AS tokenUMS
      LEFT JOIN token AS token_ins ON token_ins.ums_token = tokenUMS.id AND token_ins.work IN ($insIDs_str)
	  LEFT JOIN token AS token_del ON token_del.ums_token = tokenUMS.id AND token_del.work IN ($delIDs_str)
	
      LEFT JOIN token_parsing AS token_ins_parsing ON token_ins_parsing.token = token_ins.id
      LEFT JOIN token_parsing AS token_del_parsing ON token_del_parsing.token = token_del.id
	  
	  $leftJoinRef
    WHERE tokenUMS.work = $unifiedManuscript->id
	  AND (token_ins.id IS NOT NULL OR token_del.id IS NOT NULL)
	  AND tokenUMS.position >= $startPosition AND tokenUMS.position <= $endPosition
    ORDER BY tokenUMS.position ASC
SQL;
		$tokenQueryDuration = microtime(true);
		
		#Get the first token of the requested work
		if(preg_match('/\.1\.1$/', $osisRefStart)){ #if(strrpos($osisRefStart, '.1.1')){
			$bookStartPosition = $startPosition;
		}
		else {
			$bookStartPosition = mysql_fetch_row(mysql_query(
						"SELECT MIN(tokenUMS.position)
							FROM token tokenMS
							INNER JOIN token tokenUMS ON tokenUMS.id = tokenMS.ums_token
							INNER JOIN ref ON tokenMS.id = ref.start_token
							WHERE tokenMS.work IN ($allIDs_str) AND osis_id = \"" . mysql_real_escape_string($startBook) . "\""));
			if($bookStartPosition)
				$bookStartPosition = $bookStartPosition[0]; // + ($page-1)*TOKENS_PER_PAGE;
		}
		
		$bookEndPosition = mysql_fetch_row(mysql_query(
					"SELECT MAX(tokenUMS.position)
						FROM token tokenMS
						INNER JOIN token tokenUMS ON tokenUMS.id = tokenMS.ums_token
						INNER JOIN ref ON tokenMS.id = ref.end_token
						WHERE tokenMS.work IN ($allIDs_str) AND osis_id = \"" . mysql_real_escape_string($endBook) . "\""));
		if($bookEndPosition)
			$bookEndPosition = $bookEndPosition[0];
			
		#Do the main query
		if($view == 'unified')
			$tokenQuery = mysql_unbuffered_query($tokenSQL);
		else
			$tokenQuery = mysql_query($tokenSQL); //mysql_unbuffered_query() can't be used with mysql_data_seek()
		
		$tokenQueryDuration = microtime(true)-$tokenQueryDuration;
		
		if(mysql_errno()){
			#print "<p class='error'><em><strong>MySQL Error:</strong> " . mysql_error() . ":</em></p>";
			#print "<pre><code class='sql'>" . htmlspecialchars($tokenSQL) . "</code></pre>";
			#ob_clean();
			ob_end_clean();
			header("content-type: text/plain", true, 500);
			print mysql_error() . "\n\n" . $tokenSQL;
			exit;
		}
		### Parallel View ########################################################################################################################
		else if($view == 'parallel'){
			print "<table id='parallel'>";

			print "<thead><tr>";
			
			foreach($queriedManuscripts as &$manuscriptGroup){
				print "<th class='$manuscriptGroup->side'><header>";
				print "<h2>";
				$titles = array();
				foreach($manuscriptGroup->these as &$manuscript){
					$titles[] = "<b id='name$manuscript->id'>$manuscript->name</b>";
				}
				print (join(' + ', $titles));
				print "</h2>";
				print "</header></th>";
			}
			print "</tr></thead>";
			print "<tbody>";
			print "<tr>";
			
			foreach($queriedManuscripts as &$manuscriptGroup){
				$manuscriptGroup->wordCount = 0;
				$manuscriptGroup->uniqueWords = 0;
				
				print "<td>";

//$tokenSQL = <<<SQL
//SELECT DISTINCT
//       tokenUMS.id AS tokenUMS_id,
//       tokenUMS.data AS tokenUMS_data,
//       token1.id AS token1_id,
//	   token1.work AS token1_work,
//	   token1.data AS token1_data,
//	   token_parsing.id AS token_parsing_id,
//	   token_parsing.strongs AS token_parsing_strongs,
//	   token_parsing.lemma AS token_parsing_lemma,
//	   token_parsing.parse AS token_parsing_parse,
//	   token_parsing.work AS token_parsing_work,
//       token2.id as token2_id,
//       ref.type AS ref_type,
//       ref.numerical_start AS ref_numerical_start,
//       ref_text.data AS ref_name
//    FROM       token                     AS tokenUMS
//      LEFT JOIN token_group_cluster_token AS clusterToken1 ON clusterToken1.token = tokenUMS.id
//       LEFT JOIN token_group_cluster       AS cluster1      ON cluster1.id = clusterToken1.token_group_cluster
//        LEFT JOIN token_group               AS group1        ON group1.id = cluster1.token_group AND group1.type = 'equivalent'
//       LEFT JOIN token_group_cluster       AS cluster2      ON cluster2.token_group = group1.id
//      LEFT JOIN token_group_cluster_token AS clusterToken2 ON clusterToken2.token_group_cluster = cluster2.id
//     LEFT JOIN token                     AS token1        ON token1.id = clusterToken2.token AND token1.work IN ($theseIDs_str)
//      LEFT JOIN token_group_cluster_token AS clusterToken3 ON clusterToken3.token = tokenUMS.id
//       LEFT JOIN token_group_cluster       AS cluster3      ON cluster3.id = clusterToken3.token_group_cluster
//        LEFT JOIN token_group               AS group2        ON group2.id = cluster3.token_group AND group2.type = 'equivalent'
//       LEFT JOIN token_group_cluster       AS cluster4      ON cluster4.token_group = group2.id
//      LEFT JOIN token_group_cluster_token AS clusterToken4 ON clusterToken4.token_group_cluster = cluster4.id
//     LEFT JOIN token                     AS token2        ON token2.id = clusterToken4.token AND token2.work IN ($thoseIDs_str)
//     
//     LEFT JOIN token_parsing ON token_parsing.token = token1.id
//     LEFT JOIN ref ON ref.start_token = token1.id AND token1.work = $theseIDs[0]
//     LEFT JOIN text AS ref_text ON ref.name = ref_text.id AND language = 'en'
//     WHERE tokenUMS.work = $unifiedManuscript->id AND token1.data IS NOT NULL
//  ORDER BY tokenUMS.position ASC, ref.type ASC
//SQL;
				$inParagraph = false;
				$id = ''; #id given to the elemt which identifies it. Insertion side prefixed by 'i', deletion side prefixed with 'd'
				$book = $chapter = $verse = 0;
				$hasEquivalentToken = false;
				$bookHTML = '';
				$parsings = array();
				$chapterVerseHTML = '';
				$displayDataWorkIndex = -1;
				$displayData = '';
				$worksAttesting = array();
				$lastBook = $lastChapter = $lastVerse = $book = $chapter = $verse = 0;
				$beforeEllipsisShown = false;
				
				$thisSide = $manuscriptGroup->side;
				$thatSide = ($thisSide == 'ins' ? 'del' : 'ins');
				$theseIDs = ($thisSide == 'ins' ? $insIDs : $delIDs);
				$idPrefix = ($thisSide == 'ins' ? 'i' : 'd');
				
				$key_other_token_id = "token_{$thatSide}_id";
				$key_token_parsing_id = "token_{$thisSide}_parsing_id";
				$key_token_parsing_lemma = "token_{$thisSide}_parsing_lemma";
				$key_token_parsing_work = "token_{$thisSide}_parsing_work";
				$key_token_parsing_strongs = "token_{$thisSide}_parsing_strongs";
				$key_token_parsing_parse = "token_{$thisSide}_parsing_parse";
				$key_token_work = "token_{$thisSide}_work";
				$key_token_data = "token_{$thisSide}_data";
				
				$key_ref_type = "{$thisSide}_ref_type";
				$key_ref_osis_id = "{$thisSide}_ref_osis_id";
				#$key_ref_work = "token_{$thisSide}_work";
				
				print_adjacent_link(-1);
				
				mysql_data_seek($tokenQuery, 0); //reset query position to start at beginning
				$previousToken = null;
				$token = mysql_fetch_object($tokenQuery);
				while($token){
					if(!$id) //id used for element name
						$id = $idPrefix . $token->tokenUMS_id;
					
					//Handle reference 
					$ref_osis_id = @$token->$key_ref_osis_id;
					if(!$lastBook && !$ref_osis_id) //In case we start part-way into a verse
						$ref_osis_id = "$startBook";
					if($ref_osis_id){
						preg_match('/^(\w+)(?:\.(\d+)(?:\.(\d+))?)?/', $ref_osis_id, $matches);
						$book = $matches[1];
						$chapter = intval(@$matches[2]);
						$verse = intval(@$matches[3]);

						if($book !== $lastBook){
							$bookHTML = '';
							if($inParagraph)
								$bookHTML .= "</p>";
							$bookHTML .= "<h3>" . $osisBookNames[$book];
							
							//if($hideDivisions && $startBook == $endBook){
							//	$bookHTML .= " $startChapter:$startVerse";
							//	if($startChapter == $endChapter)
							//		$bookHTML .= "-$endVerse";
							//	else
							//		$bookHTML .= " - $endChapter:$endVerse";
							//}
							$bookHTML .= "</h3>";
							$inParagraph = true;
							$bookHTML .= "<p>";
							
							#If we're not at the first chapter and verse, then include ellipsis
							if(!$beforeEllipsisShown && $bookStartPosition != $token->tokenUMS_position){
								#$prevGET = $_GET;
								#$bookHTML .= "<a href='' rel='prev' title='See previous context'>…</a> ";
								$bookHTML .= "<strong title='There is more preceding content in this book'>…</strong> ";
								$beforeEllipsisShown = true;
							}
						}

						if(!$hideDivisions && $chapter && $verse)
							$chapterVerseHTML = "\n<sup o:osisID='$ref_osis_id'>$chapter:$verse</sup>";
						
						$lastBook = $book;
						$lastChapter = $chapter;
						$lastVerse = $verse;
					}
					
					
					if($token->$key_other_token_id)
						$hasEquivalentToken = true; #$equivalentTokens[$token->token2_id] = true;
					#if($token->token_parsing_id && $token->token_parsing_id != $tokenParsingID)
					#	$parsings[] = $token->token_parsing_raw_data;
					if($token->$key_token_parsing_id){
						$parsing = new stdClass();
						if(!empty($token->$key_token_parsing_lemma))
							$parsing->l = $token->$key_token_parsing_lemma;
						if(!empty($token->$key_token_parsing_strongs))
							$parsing->s = $token->$key_token_parsing_strongs;
						if(!empty($token->$key_token_parsing_parse))
							$parsing->p = $token->$key_token_parsing_parse;
						#$parsing->w = (int)$token->$key_token_parsing_work;
						$parsings[$token->$key_token_parsing_id] = $parsing;
					}
					if($token->$key_token_work)
						$worksAttesting[$token->$key_token_work] = true;
					
					//Use this token as the display token if it is from a work with higher precedence
					$_displayDataWorkIndex = array_search($token->$key_token_work, $theseIDs);
					if($displayDataWorkIndex == -1 || $_displayDataWorkIndex < $displayDataWorkIndex){
						$displayData = $token->$key_token_data;
						$displayDataWorkIndex = $_displayDataWorkIndex; //TODO: we could store this and return it in the results to assist with marking duplicates
					}
					
					//If there is no following token or if the next token is different from this one, then pr
					$nextToken = mysql_fetch_object($tokenQuery);
					if(!$nextToken || $token->tokenUMS_id != $nextToken->tokenUMS_id){
						
						//Print reference
						print $bookHTML;
						if(!$inParagraph){
							print "<p>";
							$inParagraph = true;
						}
						print ' ';
						print $chapterVerseHTML;
						
						//Only process a token if this side attests to it
						if(count($worksAttesting)){
							$tokenInfo[$id] = new stdClass();
							$tokenInfo[$id]->u = $token->tokenUMS_data; #$umsTokens[$token->tokenUMS_id] = $token->tokenUMS_data;
							
							$elName = 'span';
							if(!$hasEquivalentToken){
								if($manuscriptGroup->side == 'ins')
									$elName = 'ins';
								else
									$elName = 'del';
							}
							
							$class = '';
							
							//If all of the manuscripts in a group don't agree, then indicate and store this fact
							$allTheseAgree = (count($worksAttesting) == count($manuscriptGroup->these));
							if(!$allTheseAgree){
								$tokenInfo[$id]->w = array(); #$uniqueTokenWorks[$id] = array();
								foreach(array_keys($worksAttesting) as $workID)
									$tokenInfo[$id]->w[] = $workID; //$uniqueTokenWorks[$id][] = $workID;
								#print "<b>";
								#print ' class="disagreed"';
								$class = 'disagreed';
								if($elName == 'span')
									$elName = 'b';
							}
							
							//Store the UMS token for this token
							#$umsTokenByMsToken[$token->token1_id] = (int)$token->tokenUMS_id;
							
							print "<$elName";
							print " id='$id'";
							if($class)
								print " class='$class'";
							
							//If equivalent tokens exist in the other manuscript, then include them in the output
							#if(count($equivalentTokens))
							#	print " data-idrefs='t" . join(' t', $equivalentTokens) . "'"; //IDREFs
							//If there are no equivalent tokens, then this token is unique
							if(!$hasEquivalentToken)
								$manuscriptGroup->uniqueWords++;
							if(count($parsings))
								$tokenInfo[$id]->p = array_values($parsings); #$tokenParsings[$id] = array_values($parsings);
							print ">";
							print $displayData;
							$manuscriptGroup->wordCount++;
							print "</$elName>";
							#print "<sub><small>" . $token->tokenUMS_position . "</small></sub>";
							#if(!$allTheseAgree)
							#	print "</b>";
						}
						
						//$sideHasToken = false; //reset
						$equivalentTokens = array(); //reset
						$worksAttesting = array(); //reset
						$parsings = array(); //reset
						$displayDataWorkIndex = -1; //reset
						$bookHTML = ''; //reset
						$chapterVerseHTML = ''; //reset
						$tokenParsingID = 0; //reset
						$id = null; //reset
						$hasEquivalentToken = false; //reset
					}
					$previousToken = $token;
					$token = $nextToken;
				}
				if($bookEndPosition != $previousToken->tokenUMS_position){
					#$prevGET = $_GET;
					#$bookHTML .= "<a href='' rel='prev' title='See previous context'>…</a> ";
					if(!$inParagraph)
						print "<p>";
					print "&#x00A0;<strong title='There is more content in this book'>…</strong>";
				}
				if($inParagraph)
					print "</p>";
				
				print_adjacent_link(1);
				
				print "\n</td>";
			}

			print "</tr></tbody><tfoot>";
			
			
			print "<tr class='stats'>";
			foreach($queriedManuscripts as &$manuscriptGroup){
				print "<th>";
				print "Word count: $manuscriptGroup->wordCount ($manuscriptGroup->uniqueWords unique)"; #, <meter>" . (round(($manuscript->wordCount-$manuscript->uniqueWords)/$manuscript->wordCount, 4)*100) . "%</meter> in common)
				print "</th>";
			}
			print "</tr>";
			
			print "<tr class='credits'>";
			foreach($queriedManuscripts as &$manuscriptGroup){
				print "<th>";
				$count = 0;
				foreach($manuscriptGroup->these as &$manuscript){
					if($count++)
						print "<hr />";
					$license = mysql_fetch_object(mysql_query("SELECT *, (SELECT data FROM text WHERE license.name = text.id AND language = 'en') AS name
																	FROM license
																	INNER JOIN work_license ON license.id = work_license.license
																	WHERE work_license.work = $manuscript->id"));
					print "<address><cite>" . $manuscript->name . "</cite>: $manuscript->copyright</address>";
					print "<p>License: ";
					if(!$license)
						print "<em>(Unknown</em>)";
					else
						print "<a rel='license' href='" . htmlspecialchars($license->url) . "'>" . htmlspecialchars($license->name) . "</a>";
					print "</p>";
					print "<p>Source: <a href='" . htmlspecialchars($manuscript->url) . "'>" . htmlspecialchars(basename($manuscript->url)) . "</a>";
					$domain = parse_url($manuscript->url, PHP_URL_HOST);
					print " @ <a href='" . parse_url($manuscript->url, PHP_URL_SCHEME) .  "://$domain'>$domain</a></p>";
				}
				print "</th>";
			}
			print "</tr>";
			
			print "</tfoot></table>";
		}
		### Unified View ###################################################################################################################################################
		elseif($view == 'unified'){
			
			$wordCount = 0;
		
			print "<article id='unified'>";
			
			//Print the header, with the individual manuscript names marked up according to which set they appear in
			print "<header><h2>";
			$names = array();
			foreach($insManuscripts as &$manuscript){
				$names[] = "<b class='ins' id='name$manuscript->id'>" . $manuscript->name . "</b>";
			}
			print join(' + ', $names);
			print " vs. ";
			$names = array();
			foreach($delManuscripts as &$manuscript){
				$names[] = "<b class='del' id='name$manuscript->id'>" . $manuscript->name . "</b>";
			}
			print join(' + ', $names);
			unset($names);
			
			print "</h2></header>";
			
//$tokenSQL = <<<SQL
//SELECT DISTINCT
//       tokenUMS.id AS tokenUMS_id, tokenUMS.data AS tokenUMS_data,
//       token_ins.id AS token_ins_id,
//	   token_ins.work AS token_ins_work,
//       token_ins.data AS token_ins_data,
//	   token_ins_parsing.id      AS token_ins_parsing_id,
//	   token_ins_parsing.strongs AS token_ins_parsing_strongs,
//	   token_ins_parsing.lemma   AS token_ins_parsing_lemma,
//	   token_ins_parsing.parse   AS token_ins_parsing_parse,
//	   token_ins_parsing.work    AS token_ins_parsing_work,
//	   token_del.id AS token_del_id,
//	   token_del.work AS token_del_work,
//       token_del.data AS token_del_data,
//	   token_del_parsing.id      AS token_del_parsing_id,
//	   token_del_parsing.strongs AS token_del_parsing_strongs,
//	   token_del_parsing.lemma   AS token_del_parsing_lemma,
//	   token_del_parsing.parse   AS token_del_parsing_parse,
//	   token_del_parsing.work    AS token_del_parsing_work,
//	   
//       ref.type AS ref_type,
//       ref.numerical_start AS ref_numerical_start,
//       ref_text.data AS ref_name
//	   
//    FROM        token                      AS tokenUMS
//       LEFT JOIN token_group_cluster_token  AS clusterToken1 ON clusterToken1.token = tokenUMS.id
//        LEFT JOIN token_group_cluster        AS cluster1      ON cluster1.id = clusterToken1.token_group_cluster
//         LEFT JOIN token_group                AS group1        ON group1.id = cluster1.token_group AND group1.type = 'equivalent'
//        LEFT JOIN token_group_cluster        AS cluster2      ON cluster2.token_group = group1.id
//       LEFT JOIN token_group_cluster_token  AS clusterToken2 ON clusterToken2.token_group_cluster = cluster2.id
//      LEFT JOIN token                      AS token_ins     ON token_ins.id = clusterToken2.token AND token_ins.work IN ($insIDs_str)
//       LEFT JOIN token_group_cluster_token  AS clusterToken3 ON clusterToken3.token = tokenUMS.id
//        LEFT JOIN token_group_cluster        AS cluster3      ON cluster3.id = clusterToken3.token_group_cluster
//         LEFT JOIN token_group                AS group2        ON group2.id = cluster3.token_group AND group2.type = 'equivalent'
//        LEFT JOIN token_group_cluster        AS cluster4      ON cluster4.token_group = group2.id
//       LEFT JOIN token_group_cluster_token  AS clusterToken4 ON clusterToken4.token_group_cluster = cluster4.id
//      LEFT JOIN token                      AS token_del     ON token_del.id = clusterToken4.token AND token_del.work IN ($delIDs_str)
//      
//      LEFT JOIN token_parsing AS token_ins_parsing ON token_ins_parsing.token = token_ins.id
//      LEFT JOIN token_parsing AS token_del_parsing ON token_del_parsing.token = token_del.id
//      LEFT JOIN ref ON ref.start_token = token_ins.id
//      LEFT JOIN text AS ref_text ON ref.name = ref_text.id AND language = 'en'
//    WHERE tokenUMS.work = $unifiedManuscript->id AND (token_ins.id IS NOT NULL OR token_del.id IS NOT NULL) 
//    ORDER BY tokenUMS.position ASC, ref.type ASC
//SQL;
			
			$insUniqueWordCount = $delUniqueWordCount = 0;
			$token_ins_id = $token_del_id = 0;
			$token_ins_data = $token_del_data = '';
			/*$display_token_id = 0;*/
			$display_token_data = '';
			$displayDataWorkIndex = -1;
			
			$works_ins_attesting = array();
			$works_del_attesting = array();
			
			$parsings_ins = array();
			$parsings_del = array();
			
			$inParagraph = false;
			$lastBook = $lastChapter = $lastVerse = $book = $chapter = $verse = 0;
			$bookHTML = '';
			$chapterVerseHTML = '';
			$equivalentTokens = array(); #NOTE: We need to check for equivalent
			$refParsed = false;
			$beforeEllipsisShown = false;
			
			print_adjacent_link(-1);
			
			$previousToken = null;
			$token = mysql_fetch_object($tokenQuery);
			while($token){
				$id = 't' . $token->tokenUMS_id;
				if(!isset($tokenInfo[$id]))
					$tokenInfo[$id] = new stdClass();
				
				//Handle references
				$ref_osis_id = @$token->ins_ref_osis_id;
				if(!$lastBook && !$ref_osis_id) //In case we start part-way into a verse
					$ref_osis_id = "$startBook";
				if(!$refParsed && $ref_osis_id){
					preg_match('/^(\w+)(?:\.(\d+)(?:\.(\d+))?)?/', $ref_osis_id, $matches);
					$book = $matches[1];
					$chapter = intval(@$matches[2]);
					$verse = intval(@$matches[3]);
					if($book !== $lastBook){
						$bookHTML = '';
						if($inParagraph)
							$bookHTML .= "</p>";
						$bookHTML .= "<h3>" . $osisBookNames[$book] . "</h3>";
						$inParagraph = true;
						$bookHTML .= "<p>";
						
						#If we're not at the first chapter and verse, then include ellipsis
						if(!$beforeEllipsisShown && $bookStartPosition != $token->tokenUMS_position){
							#$prevGET = $_GET;
							#$bookHTML .= "<a href='' rel='prev' title='See previous context'>…</a> ";
							$bookHTML .= "<strong title='There is more preceding content in this book'>…</strong> ";
							$beforeEllipsisShown = true;
						}
					}
					if(!$hideDivisions && $chapter && $verse)
						$chapterVerseHTML = "\n<sup o:osisID='$token->ins_ref_osis_id'>$chapter:$verse</sup>";
					
					$lastBook = $book;
					$lastChapter = $chapter;
					$lastVerse = $verse;
					$refParsed = true;
				}
				
				//Store which token is used by either manuscript
				if($token->token_ins_id){
				
					//Use this token as the display token if it is from a work with higher precedence
					$_displayDataWorkIndex = array_search($token->token_ins_work, $allIDs);
					if($displayDataWorkIndex == -1 || $_displayDataWorkIndex < $displayDataWorkIndex){
						$display_token_data = $token->token_ins_data;
						$displayDataWorkIndex = $_displayDataWorkIndex;
					}
					$works_ins_attesting[$token->token_ins_work] = true;
				}
				if($token->token_del_id){
				
					//Use this token as the display token if it is from a work with higher precedence
					$_displayDataWorkIndex = array_search($token->token_del_work, $allIDs);
					if($displayDataWorkIndex == -1 || $_displayDataWorkIndex < $displayDataWorkIndex){
						$display_token_data = $token->token_del_data;
						$displayDataWorkIndex = $_displayDataWorkIndex;
					}
					$works_del_attesting[$token->token_del_work] = true;
				}
				
				//Store parsings
				if(!empty($token->token_ins_parsing_id)){
					$parsing = new stdClass();
					if(!empty($token->token_ins_parsing_lemma))
						$parsing->l = $token->token_ins_parsing_lemma;
					if(!empty($token->token_ins_parsing_strongs))
						$parsing->s = $token->token_ins_parsing_strongs;
					if(!empty($token->token_ins_parsing_parse))
						$parsing->p = $token->token_ins_parsing_parse;
					#$parsing->w = (int)$token->token_ins_parsing_work;
					$parsings_ins[$token->token_ins_parsing_id] = $parsing;
				}
				if(!empty($token->token_del_parsing_id)){
					$parsing = new stdClass();
					if(!empty($token->token_del_parsing_lemma))
						$parsing->l = $token->token_del_parsing_lemma;
					if(!empty($token->token_del_parsing_strongs))
						$parsing->s = $token->token_del_parsing_strongs;
					if(!empty($token->token_del_parsing_parse))
						$parsing->p = $token->token_del_parsing_parse;
					#$parsing->w = (int)$token->token_del_parsing_work;
					$parsings_del[$token->token_del_parsing_id] = $parsing;
				}
				
				//If there is no following token or if the next token is different from this one, then print the token
				$nextToken = mysql_fetch_object($tokenQuery);
				if(!$nextToken || $token->tokenUMS_id != $nextToken->tokenUMS_id){
					$tokenInfo[$id]->u = $token->tokenUMS_data; //$umsTokens[$token->tokenUMS_id] = $token->tokenUMS_data;
					
					$equivalentTokens = array_keys($equivalentTokens);
					
					print $bookHTML;
					$bookHTML = ''; //reset
					if(!$inParagraph){
						print "<p>";
						$inParagraph = true;
					}
					print ' ';
					print $chapterVerseHTML;
					
					$works_ins_attesting_count = count($works_ins_attesting);
					$works_del_attesting_count = count($works_del_attesting);
					
					if(!$works_ins_attesting_count){
						$delUniqueWordCount++;
						$elName = 'del';
					}
					else if(!$works_del_attesting_count){
						$insUniqueWordCount++;
						$elName = 'ins';
					}
					else
						$elName = 'span';
					$class = '';
					
					//If all of the manuscripts in a group don't agree, then indicate and store this fact
					$allTheseAgree = (
						(!$works_ins_attesting_count || $works_ins_attesting_count == count($insManuscripts))
						&&
						(!$works_del_attesting_count || $works_del_attesting_count == count($delManuscripts))
					);
					if(!$allTheseAgree){
						$tokenInfo[$id]->w = array(); //$uniqueTokenWorks[$token->tokenUMS_id /*$display_token_id*/] = array();
						foreach(array_keys($works_ins_attesting) as $workID){
							$tokenInfo[$id]->w[] = $workID; //$uniqueTokenWorks[$token->tokenUMS_id /*$display_token_id*/][] = $workID;
						}
						foreach(array_keys($works_del_attesting) as $workID){
							$tokenInfo[$id]->w[] = $workID; //$uniqueTokenWorks[$token->tokenUMS_id /*$display_token_id*/][] = $workID;
						}
						if($elName == 'span')
							$elName = 'b';
						$class = 'disagreed';
						#print "<b>";
					}
					
					if(!empty($parsings_ins) || !empty($parsings_del))
						$tokenInfo[$id]->p = array_values(array_merge($parsings_ins, $parsings_del)); //$tokenParsings[$token->tokenUMS_id /*$display_token_id*/] = array_values(array_merge($parsings_ins, $parsings_del));
						
					//Store the UMS token for this token
					#$umsTokenByMsToken[$token->tokenUMS_id /*$display_token_id*/] = (int)$token->tokenUMS_id;
					
					$chapterVerseHTML = ''; //reset
					print "<$elName id='t{$token->tokenUMS_id}'";
					#print " data-ums=\"" . $token->tokenUMS_data . "\"";
					if($class)
						print " class='$class'";
					print ">";
					print $display_token_data;
					#print $token_ins_data ? $token_ins_data : $token_del_data;
					#print $token->token_ins_data . ':' . $token->token_del_data;
					$wordCount++;
					print "</$elName>";
					
					#if(!$allTheseAgree)
					#	print "</b>";
					
					$token_ins_id = $token_del_id /*= $display_token_id*/ = 0; //reset
					$token_ins_data = $token_del_data = $display_token_data = ''; //reset
					$parsings_ins = array(); //reset
					$parsings_del = array(); //reset
					$works_ins_attesting = array(); //reset
					$works_del_attesting = array(); //reset
					$displayDataWorkIndex = -1; //reset
					$refParsed = false;
				}
				$previousToken = $token;
				$token = $nextToken;
			}
			if($bookEndPosition != $previousToken->tokenUMS_position){
				#$prevGET = $_GET;
				#$bookHTML .= "<a href='' rel='prev' title='See previous context'>…</a> ";
				if(!$inParagraph)
					print "<p>";
				print "&#x00A0;<strong title='There is more content in this book'>…</strong>";
			}
			if($inParagraph)
				print "</p>";
			
			print_adjacent_link(1);
			
			print "\n<footer>";
			print "Words inserted: $insUniqueWordCount. Words deleted: $delUniqueWordCount.";
			print "</footer>";
			
			$allManuscripts = array_merge($insManuscripts, $delManuscripts);
			foreach($allManuscripts as &$manuscript){
				$license = mysql_fetch_object(mysql_query("SELECT *, (SELECT data FROM text WHERE license.name = text.id AND language = 'en') AS name FROM license INNER JOIN work_license ON license.id = work_license.license WHERE work_license.work = $manuscript->id"));
				print '<footer>';
				print "<address><cite>" . $manuscript->name . "</cite>: " . $manuscript->copyright . "</address>";
				print "<p>License: ";
				if(!$license)
					print "<em>(Unknown</em>)";
				else
					print "<a rel='license' href='" . htmlspecialchars($license->url) . "'>" . htmlspecialchars($license->name) . "</a>";
				print "</p>";
				print "<p>Source: <a href='" . htmlspecialchars($manuscript->url) . "'>" . htmlspecialchars(basename($manuscript->url)) . "</a>";
				$domain = parse_url($manuscript->url, PHP_URL_HOST);
				print " @ <a href='" . parse_url($manuscript->url, PHP_URL_SCHEME) .  "://$domain'>$domain</a></p>";
				print "</footer>";
			}
			print '</article>';
			
		} //end unified view

		print "\n";
		print "\t\t<script>/*<![CDATA[*/\n";
		#$js = "\t\tvar tokenParsings = " . json_encode($tokenParsings) . ";\n" .
		#      "\t\tvar uniqueTokenWorks = " . json_encode($uniqueTokenWorks) . ";\n" .
		#      "\t\tvar umsTokens = " . json_encode($umsTokens) . ";\n";
		print "\t\t//p=parsings (p=parse, l=lemma, s=strongs), w=uniqueTokenWorks, u=tokenFromUMS\n";
		$js = "\t\tvar tokenInfo = " . json_encode($tokenInfo) . ";\n";
		print html_entity_decode(preg_replace('/\\\\u([0-9a-f][0-9a-f][0-9a-f][0-9a-f])/', '&#x$1;', $js), ENT_NOQUOTES, 'UTF-8'); #Convert escaped Unicode to UTF-8 to compact file size
		print "\t\t//]]></script>\n";
		
		//Output all of the tokenParsings
		if($includeStrongs)
			print "\t\t<script src='../strongs-greek-dictionary.js?" . filemtime('../strongs-greek-dictionary.js') . "'></script>";

		print "\n<!--\n";
		print "Total rows: " . mysql_num_rows($tokenQuery) . "\n";
		print "Uncached query time: $tokenQueryDuration secs\n";
		#print "Script time: " . ((microtime(true) - $startTime) - $tokenQueryDuration) . " secs\n";
		print "-->\n";
		
		if($requestCacheKey)
			file_put_contents("cache/$requestCacheKey", ob_get_flush());
	} //end uncached results

		print "\n<!--\n";
		#print "Total rows: " . mysql_num_rows($tokenQuery) . "\n";
		#print "Query time: $tokenQueryDuration secs\n";
		print "Script time: " . ((microtime(true) - $startTime) - $tokenQueryDuration) . " secs\n";
		print "-->\n";
	
} //end request ?>

		<hr />
		<footer>
			<address>
				Copyright 2009, <a href="http://openscriptures.org/" rel="home">OpenScriptures.org</a>. 
			</address>
			<p>
				Code open source under <a href="http://www.gnu.org/licenses/gpl.html" rel="license">GPL 3</a>.
				<!--Derivative content and contributor -->Content licensed <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution-Noncommercial-Share Alike 3.0</a> if permitted by supporting works.
				<span class="host">Hosted by <a href="http://shepherd-interactive.com/">Shepherd Interactive</a>.</span>
			</p>
			<p class='repo'><?php print format_svn_id('$Id: index.php 214 2009-05-18 02:29:25Z WestonRuter@gmail.com $', '$URL: http://open-scriptures.googlecode.com/svn/tags/0.1/web/prototypes/manuscript-comparator/index.php $'); ?></p>
		</footer>
	
		<?php if(file_exists('../jquery.min.js')): ?>
		<script src="../jquery.min.js?ver=1.3.2"></script>
		<?php else: ?>
		<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
		<?php endif; ?>
		<script src="../jquery-ui-personalized-1.6rc6.min.js"></script>
		<!--<script src="../jquery.cookie.js"></script>-->
		<!--<script src="../reference-parser.js?<?php //echo filemtime('../reference-parser.js') ?>"></script>-->
		<script src="main.js?<?php echo filemtime('main.js') ?>"></script>
		
		<!-- Google Analytics -->
		<script src="http://www.google-analytics.com/ga.js"></script>
		<script type="text/javascript">
		try {
			var pageTracker = _gat._getTracker("UA-6816157-1");
			pageTracker._trackPageview();
		} catch(e) {}
		</script>
	</body>
</html>